///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2006, Industrial Light & Magic, a division of Lucas
// Digital Ltd. LLC
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// *       Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// *       Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// *       Neither the name of Industrial Light & Magic nor the names of
// its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////

/*
-------------------------------------------------------------------------------
CreateDLL.exe
-------------------------------------------------------------------------------
version 1.0
-------------------------------------------------------------------------------

Nick Porcino
Osaka, Dec 23/2004

-------------------------------------------------------------------------------

Create a DLL from an MSVC linked library with all public symbols exported.
Makes a DLL have the same link semantics as a *nix DSO.

Eliminates the need to declspec everything.
This program also successfully exports templated objects, and all STL objects.

-------------------------------------------------------------------------------
No warranty expressed or implied, supplied as is including all faults!
-------------------------------------------------------------------------------

You must have already created a DLL using MSVC. Specify the MAP option when
you build the DLL.

---------
WARNING:

Also DO NOT FORGET to specify the *linker* option /DEBUG in your project settings
because CreateDLL needs this: without /DEBUG, the linker may omit any
object files which do not export functions, i.e. specify functions which are
not attributed with __declspec(dllexport) nor appear in any original .DEF file:
when compiling with compiler /GL and (optionally) linker /LTCG options, the initial
linker run (which creates the MAP file for CreateDLL) will NOT list those 
'non-exporting' objectfiles, resulting in 'missing symbol' errors in the linker
run initiated by CreateDLL as our tool won't have had a chance to find all
required object files in the original MAP file. Hence: make sure the *linker*
/DEBUG flag is turned on in your MSVC projects, even for Release versions.
CreateDLL will re-run the linker without that /DEBUG flag anyhow, so you don't have
to worry that the final produced DLL is cluttered with unwanted debug info.
---------

This program
    reads in MAP file generated by your initial link,
    parses all the symbols,
    prepares a DEF file with the mangled symbols
    sniffs for required OBJS and LIBS
    creates a linker response file
    relinks your DLL using the new DEF file

you must set up an environment variable called MSVCLINK which is the full path
to the MSVC linker. eg:
     C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\link.exe

If you have a BOOST_ROOT environment variable, CreateDLL will automatically
include BOOST_ROOT\stage\lib in the link path.

the arguments for CreateDLL are-

    full path to the .map file.

    full path to the place where you keep all your libs that you will link against.

    name of the generated import library. Typically it will be the same as the
    dll, eg Imath.dll + Imath.lib. However, if the library you're working on has
    a DLL version, and a static lib version, you will probably want to distinguish
    the name of the import library like this: ImathDLL.lib, or similar, so that
    there is no confusion about what the lib actually is.

Use quotes if there are spaces. Otherwise, quotes are optional.

Example

CreateDLL "C:\coding\vc7\Dev\Ae\Ae.map" "C:\coding\objects" "C:\coding\objects\Ae.lib"

objects is the place where all the .libs have been copied to. If you have a lot
of third party bits, eg., jpeg.lib, copy them there too.

Now your DLL has all its symbols exported! The new DLL will overwrite the
original DLL in place.

-------------------------------------------------------------------------------

Extra Magic: template instantiation

You may need some extra magic to export some templates.

If you get a link error that indicates that a templated class is not found -

Let's say you have templated class Foo, specialized on int, and Foo<int> is
needed to link your DLL.

You will need a preprocessor definition such as MYMODULE_EXPORTS which
indicates that your module is being used. Please be aware that the old schtick
of a DLL_EXPORT is not sufficient if you have more than one DLL in play. Use
this method instead.

#ifdef MSC_VER
#ifndef MYMODULE_EXPORTS
extern template class Foo<int>;
#endif
#endif

This generates a warning, but it is the microsoft sanctioned solution to this
problem.

In Foo.cpp you must also

#ifdef MSC_VER
template class Foo<int>;
#endif

-------------------------------------------------------------------------------

Extra Magic: static member variables

This program does not fix the problem that the MS dynamic loader can't
resolve static member variables of classes properly. This issue will be
the most important thing to watch out for.

example -
class Foo
{
public:
static int bar;
};

the dynamic loader puts bar in the wrong memory space. This is a fundamental
problem with the architecture of DLL's. There is no workaround, except to
change the code; example, in the header file -

#ifdef MSC_VER
extern __declspec(dllexport) int foo_bar;
#endif

and in the .cpp file,

#ifdef MSC_VER
__declspec(dllexport) int foo_bar;
#endif

You'll get multiple declarations of foo_bar, but the linker will resolve
them down to one when the DLL is linked.

-------------------------------------------------------------------------------

Extra Magic: exporting globals

Finally, you'll still need to use declspecs for global variables.

example -
see static member variables example for foo_bar.

-------------------------------------------------------------------------------

Extra Magic: vector deleting destructors

If you REALLY need to use a vector deleting destructor, you'll need to
declspec your class, and all the nested classes, in the traditional way.

What's the issue? The linker strips all vector deleting destructors whether
you want it to or not. declspecing the class provides a default implementation,
as opposed to the one you really want.

This is a built-in hack in the linker dating back to VC5. The only workaround
is to use STL vectors instead, or don't use vector deleting destructors at all.

example -

class Foo
{
public:
   ~Foo(int size) { } // linker will discard. it can't be done.
};

-------------------------------------------------------------------------------
version 1.2
-------------------------------------------------------------------------------
Ger Hobbelt

- Added 64-bit build support: -M commandline option now enables CreateDLL to
  create suitable response files for X64 (AMD64) and IA64 (Itanium) 64-bit
  builds of target DDLs.

- Added --manifest commandline option, so you can specify a manifest filename.
  This automatically will instruct the linker to produce a manifest for the
  target DLL. Default is: no manifest.

  If you do not specify a filename with '--manifest' a manifest filename will
  be constructed for you, based on the MAP filename,

- Added '-v' for verbose output; this helps when trying to diagnose problems
  with CreateDLL and the call to the Microsoft Linker as done by CreateDLL.

-------------------------------------------------------------------------------
version 1.2.1
-------------------------------------------------------------------------------
Ger Hobbelt

- Got rid of the redundant GetSymbols() code; changed CreateDEF() to use the
  already collected symbols.

- Added the '-I' commandline option; you can specify any .DEF formatted file
  which contains exported identifiers and numeric ordinals in the format

    <tab>identifier<tab>@ordinal<newline>

  where <tab> is one or more whitespace characters and 'ordinal' is an integer
  number.

  This is added to provide support for the creation of DLLs which have to
  export some or all identifiers as fixed ordinals as well.

-------------------------------------------------------------------------------
version 1.2.2
-------------------------------------------------------------------------------
Ger Hobbelt

- added the 'linker with mandatory /DEBUG flag' warning above; tiny fix for
  loading multiple DEF files (which are only used to extract ordinals from them).

-------------------------------------------------------------------------------
version 1.2.3
-------------------------------------------------------------------------------
Ger Hobbelt

- added the '-s' / '--skip' skip-linker-invocation commandline argument:
  This acts similar to a no-op (linker is not run), so CreateDLL will not
  relink/recreate your DLL -- this is useful when checking/testing behavioural
  issues with / without CreateDLL in your compile/link/postbuild pipeline.
  
  (Hint: if you like to check the 'original' MAP file as created by the
         default link cycle, this is your option!)

- Added extra LINK exit code checking: previously CreateDLL would always
  report 'linking success', even when the linker (as invoked by CreateDLL)
  spit out an error report.

  CreateDLL now propagates the linker exit code to the caller. This is useful
  in both Makefile and MSVC PostBuild usage scenarios.

- MessageBox() --> MessageBoxA() for building on Unicode dev systems.

-------------------------------------------------------------------------------
version 1.2.4
-------------------------------------------------------------------------------
Ger Hobbelt

- Added support for exported names, i.e. /EXPORT exportname=internal_name
  (This is, for example, needed for Excel plugin DLLs)

  Consequently, support for .DEF files has been enhanced as well:
  You can specify any .DEF formatted file which contains exported identifiers 
  and numeric ordinals in the format

    <tab>identifier[=external_name]<tab>@ordinal<newline>

  where <tab> is one or more whitespace characters, 'ordinal' is an integer
  number and [=external_name] is an extra optional part where 'external_name'
  is defined as the name under which to export the internal function 'identifier'.

-------------------------------------------------------------------------------
version 1.2.5
-------------------------------------------------------------------------------
Ger Hobbelt

- Augmented '-o', etc. (i.e. all collection) options to APPEND multple occurrences
  on the command line.
  Formerly a command line like
  
    CreateDLL ... -o a.lib -o b.lib
	
  would discard any but the last '-o' entry, thus discarding [a.lib] from the 
  library load list. Of course, there's this:
  
    CreateDLL ... -o a.lib,b.lib

  but that becomes a bit tough to read when the lib paths have spaces (with 
  double quotes surrounding each path) and/or long paths. It's far easier for the 
  invoker when we would 'append [b.lib] to the list' when invoked this way:
  
    CreateDLL ... -o a.lib -o b.lib
	
  Hence now all collection options (-I, -o, -l) support both the last and last-but-one
  format (the separator may be comma or semicolon)

-------------------------------------------------------------------------------
version 1.2.6
-------------------------------------------------------------------------------
Ger Hobbelt

- Nothing much, just removed the hacky IlmBaseConfig.h dependency from the source
  code again. Functionality is identical to v1.2.5.

-------------------------------------------------------------------------------
version 1.2.7
-------------------------------------------------------------------------------
Ger Hobbelt

- Unify all paths to use DIRSEP ('\\') as path separator everywhere.
- Add -Wpath argument to assign a work directory other than the system's tempdir.
  
===============================================================================
*/



//#include "IlmBaseConfig.h" /* [i_a] rude MSVC2005 hack */
#include "OptionParser.h"
#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>
#include <hash_map>
#include <set>
#include <string>
#include <process.h>                // for _spawnvp()
#include <windows.h>                // needed to create processes
#include <sys/stat.h>
#include <time.h>					// required by MSVC2008

using std::cerr;
using std::cout;
using std::endl;
using std::set;
using std::string;
using std::vector;
using stdext::hash_map;

using namespace std;


//#define DEBUGGING
#define MAX_ARGS 1000

#define DIRSEP '\\'

struct extra_symbol_info
{
	int ordinal;
	string export_alt_name;

	extra_symbol_info(): 
		ordinal(0)
	{}
};

typedef hash_map<string, extra_symbol_info *> symbol_store;

char* filterSymbols[] = {
    "__real@",
    "??_",
    "_== ",         // a strange symbol from fltk
    "AEPAXI@Z",         // all vector deleting destructors have this pattern in them
    "boost@@2_NB",      // a boost template metaprogramming side effect that generates 1000's of unneeded symbols
    //"??0out_of_range@std@@QAE@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@1@@Z"
    //"??1out_of_range@std@@UAE@XZ",
    //"??0out_of_range@std@@QAE@ABV01@@Z",
    "out_of_range@std@@",
    "?_Checked_iterator_base@?$_Vector_const_iterator@",
    "??$_Checked_base@V?$_Vector_const_iterator@",
    "??0?$_Vector_const_iterator@",
    "??0?$_Vector_iterator@",
    //"??$_Uninit_copy@PBV?$vector@_KV?$allocator@_K@std@@",
    //"??$_Uninit_copy@PB_KPA_KV?$allocator@_K@std@@",
    "??$_Uninit_copy@PB",
    "??0?$_Tree_ptr@V?$_Tmap_traits@",
    "?_Myptr@?$basic_string@DU?$char_traits@D@std@@",
    "__imp_??1_Container_base@std", // non-existent symbols in the std
    0
};


/*
   See samples and comment at getSymbolsObjsAndLibsFromMap(). This set can start a 
   valid label:

   .?@_A-Za-z
 */
static bool isStartSymbol(char c)
{
    return !!strchr(".@?_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", c);    
	// ? are C++ symbols
	// _, etc. are either C++ or C symbols. it depends...
}

static bool isWhitespace(char c)
{
    return (c == ' ') || (c == '\t') || (c == 0xa) || (c == 0xd) || (c == 26);
}


#ifndef max
static inline int max(int a, int b)
{
	if (a > b)
		return a;
	return b;
}
#endif


/*
Replace any '*' with a random number to create a unique filename which doesn't exist yet.
*/
static string mk_tempname(const char *template_str)
{
	time_t t = time(NULL);
	struct stat st;
	unsigned long int cb = (unsigned long int)t;

	for (;;)
	{
		unsigned long int c = cb++;
		string rv;
		for (const char *s = template_str; *s; s++)
		{
			if (*s == '*')
			{
				char buf[40];
				sprintf(buf, "%lu", c);
				c = rand();
				rv.append(buf);
			}
			else
			{
				rv.push_back(*s);
			}
		}
		if (stat(rv.c_str(), &st))
		{
			return rv;
		}
	}
}

static string unify_path(const char *src)
{
	string rv;

	for (const char *s = src; *s; s++)
	{
		if (*s == '/' || *s == '\\')
			rv.push_back(DIRSEP);
		else
			rv.push_back(*s);
	}
	return rv;
}

static string terminate_dir(const char *path)
{
	string rv = unify_path(path);

	if (!rv.empty() && DIRSEP != *--rv.end())
		rv.push_back(DIRSEP);
	return rv;
}

static string unify_path(string &src)
{
	return unify_path(src.c_str());
}
static string terminate_dir(string &path)
{
	return terminate_dir(path.c_str());
}
static string mk_tempname(string &template_str)
{
	return mk_tempname(template_str.c_str());
}

void createDef(string moduleName, symbol_store &strings, bool verbose, FILE* defFile)
{
    if (strings.size() > 0)
    {
        symbol_store::iterator i;

        cout << "CreateDLL found " << (int) strings.size() << " symbols" << endl;

        fprintf(defFile, "LIBRARY\t%s\nEXPORTS\n", moduleName.c_str());
        if (verbose)
        {
            cerr << "----- Creating DEF file: -----" << endl;
            cerr << "LIBRARY\t" << moduleName.c_str() << endl << "EXPORTS" << endl;
        }

        for (i = strings.begin(); i != strings.end(); i++)
        {
            if (i->second == 0)
            {
                fprintf(defFile, "\t%s\n", i->first.c_str());
                if (verbose)
                {
                    cerr << "\t" << i->first.c_str() << endl;
                }
            }
            else if (i->second->export_alt_name.empty())
            {
			// Jihun July, 2010 : ordinals cause a link warning of LNK4197 on win64
			// when a c function is declared with _declspec(dllexport) and in the .def file.
			// removing ordinal fixes this problem.

				int spacing = max(8, 60 - (int)i->first.size());

                fprintf(defFile, "\t%s%*s   @%d\n", i->first.c_str(), spacing, "", i->second->ordinal);
                if (verbose)
                {
                    cerr << "\t" << i->first.c_str() << setw(spacing) << "   @" << i->second->ordinal << endl;
                }
            }
            else if (i->second->ordinal == 0)
            {
                fprintf(defFile, "\t%s=%s\n", i->first.c_str(), i->second->export_alt_name.c_str());
                if (verbose)
                {
                    cerr << "\t" << i->first.c_str() << " = " << i->second->export_alt_name.c_str() << endl;
                }
            }
            else
            {
				int spacing = max(8, 60 - (int)i->first.size() - 1 - (int)i->second->export_alt_name.size());

                fprintf(defFile, "\t%s=%s%*s   @%d\n", i->first.c_str(), i->second->export_alt_name.c_str(), spacing, "", i->second->ordinal);
                if (verbose)
                {
                    cerr << "\t" << i->first.c_str() << " = " << i->second->export_alt_name.c_str() << setw(spacing) << "   @" << i->second->ordinal << endl;
                }
            }
        }

        if (verbose)
        {
            cerr << "----- end of DEF file -----" << endl;
        }
    }
}



static bool isEOF(char c)
{
    return (c == '\0') || (c == 26);
}






static void addLibsFromVector(set<string>& libs, vector<string>& morelibs)
{
    for (vector<string>::const_iterator i = morelibs.begin(); i != morelibs.end(); ++i)
    {
        std::string temp = *i;
	    string::size_type pos = temp.find(".lib");
        if (pos == string::npos)
        {
            temp += ".lib";
        }
		temp = unify_path(temp);

        if (libs.find(temp) == libs.end())
        {
            libs.insert(temp);
        }
    }
}






/*
   MAP file sample lines which should be recognized:

   Win32:

 0003:000035ec       __CTA8?AVEintrExc@Iex@@    1001f5ec     IexThrowErrnoExc.obj
 0002:00007bf0       _DllMain@12                10018bf0 f   MSVCRTD:dllmain.obj
 0002:00006280       @__security_check_cookie@4 10017280 f   MSVCRTD:secchk.obj
 0002:000061c0       ??_Etype_info@@UAEPAXI@Z   100171c0 f i MSVCRTD:ti_inst.obj
 0003:0000053c       ??_C@_0P@GHFPNOJB@bad?5allocation?$AA@ 1001c53c     IexBaseExc.obj
 0005:00000254       __imp__GetModuleFileNameW@12 10022254     kernel32:KERNEL32.dll
 0002:00006970       _DebuggerKnownHandle       10017970 f   MSVCRTD:error.obj

   
   Win64 (AMD64):

 0002:000059f0       _CTA8?AVEintrExc@Iex@@     00000001800119f0     IexThrowErrnoExc.obj
 0001:000074e0       DllMain                    00000001800084e0 f   MSVCRTD:dllmain.obj
 0001:00005af0       __security_check_cookie    0000000180006af0 f   MSVCRTD:amdsecgs.obj
 0001:00005960       ??_Etype_info@@UEAAPEAXI@Z 0000000180006960 f i MSVCRTD:ti_inst.obj
 0005:00000450       __imp_GetModuleFileNameW   0000000180016450     kernel32:KERNEL32.dll
 0001:00006740       DebuggerKnownHandle        0000000180007740 f   MSVCRTD:error.obj

   
   Win64 (Itanium):

 0001:00002a20       .??_GBaseExc@Iex@@UEAAPEAXI@Z 0000000180004a20 f i IexBaseExc.obj
 0001:00016dc0       ._malloc_dbg               0000000180018dc0 f   MSVCRTD:MSVCR80D.dll
 0002:000099f0       _CTA8?AVEintrExc@Iex@@     000000018002d9f0     IexThrowErrnoExc.obj
 0001:00017ca0       .DllMain                   0000000180019ca0 f   MSVCRTD:dllmain.obj
 0001:00011900       .__security_check_cookie   0000000180013900 f   MSVCRTD:secchk.obj
 0002:00001e08       ??_7type_info@@6B@         0000000180025e08     MSVCRTD:ti_inst.obj
 0005:00000070       __imp_GetModuleFileNameW   0000000180034070     kernel32:KERNEL32.dll
 0001:000133e0       .DebuggerKnownHandle       00000001800153e0 f   MSVCRTD:error.obj
 

   and a few ones we should discard:

 0001:00000000       __enc$textbss$begin        10001000     <linker-defined>
 0005:00000050 000001ccH .idata$4                DATA
 0001:00000000 0001c170H .text                   CODE
 0002:00000040       \177ADVAPI32_NULL_THUNK_DATA 10143040     advapi32:ADVAPI32.dll
 entry point at        0002:0000034d


   Note that on Win64 (AMD64, Itanium), the extra underscore ('_') for C functions is MISSING!

   Also note that not only can _ or ? start a label, '@' can too. Even '.' can start a valid symbol
   (see Itanium list above). That last one should trigger a red light in your head: better
   make darn sure the CreateDLL tool code does NOT include those ('.')dot-prefixed segment symbols from
   the map file!

   Some C functions have a __stdcall (pascal) calling convention, which is recognizable
   by those functions having a @n tail, where n is the number of bytes pushed on the stack for
   the args. This may seen like a real bugger as this is not easily discernible from 
   C++ encoded labels, which can also contain one or more '@', but it turns out that the 
   linker is fine with this in a map file.
   
   All relevant labels have their lib/dll/obj listed at the end of the line.

   'external' labels have a 'owner:file' format at the end, while 'exports' just have 'file',
   where 'file' == 'xyz.obj'.

   Note that for external labels, we'll need to address the 'owner' (postfixed with .lib) and
   NOT the :file.obj or :file.dll in there. Which makes life somewhat easier.

   Forget about those single 'f', 'i' and other markers in the map file lines. Ignore them.


   Ordinals and 'exported name' exports (where the exported name does NOT equal the internal 
   name) are all listed in a separate section in the MAP file: 'Exports' , which comes after
   the EOF ('Static symbols' section).


   WARNING: this code will DAMAGE the contents of buf[] beyond repair!
*/

static void getSymbolsObjsAndLibsFromMap(char* buf, const int length, 
										 bool C_symbols_have_underscore_prefix, bool verbose,
										 symbol_store& strings, set<string>& objs, 
										 set<string>& libs, set<string>& static_libs)
{
    const char* eof = strstr(buf, "Static symbols");
    if (eof == 0) {
        eof = buf + length;
    }
	char *end_of_file = buf + length;

	int lineno = 0;

    while (buf < eof) {
		char *line_start = buf;

		lineno++;

        // find a symbol
		//
		// all 'interesting' lines start with an address: 
		// hex digits and an optional ':'.
		// Skip that first!
		while (buf < eof && isWhitespace(*buf))
		{
			buf++;
			if (buf[-1] == '\n')
				break;
		}
		if (buf != line_start && buf[-1] == '\n')
			continue; // extra tweak: keep linecount healthy.

		while (buf < eof && (isxdigit(*buf) || (*buf == ':')))
			buf++;
		// now we're past the (possible) address at the front of the line: see if we can find a symbol here:
        while (buf < eof) {
            ++buf;
            if (isStartSymbol(*buf))
                break;
        }

        // if preceding character is not ' ', skip to end of line
		// UNLESS we've hit a HEX digit (a-fA-F) which can also
		// start a valid symbol: in that case we skip a WORD
        if (*(buf-1) != ' ') {
            char *end = buf + strcspn(buf, "\n");
			*end++ = 0;

			if (verbose)
			{
				cerr << "skipped MAP line " << lineno << " :" << line_start << endl;
			}

			buf = end;
        }
		else {
	        // do NOT skip double-underscore-prefixed symbols as those are _perfectly_ okay.
			// Sure, they 'are reserved for compiler/run-time library use' but has that
			// ever stopped anyone?
			// Besides, we need to inspect them anyway in case they're EXTERNAL references
			// and we need to collect that library reference on that line.

            // got a symbol
            char* end = buf;
            while (end < eof) {
                ++end;
                if (*end == ' ')
                    break;
            }

            if (end < eof) {
                // is it an import or export symbol?
                char* lineend = end + strcspn(end, "\n");
				char *le = lineend; // copy pointer: this is either the end of the line or end of buf

				lineend--; // if end of buf, step before the NUL; otherwise: step before the '\n'

				// eat all the whitespace at the end
				while (isWhitespace(*lineend))
				{
					--lineend;
				}
				++lineend;

				*lineend = '\0';

				if (verbose)
				{
					cerr << "inspecting MAP line " << lineno << " :" << buf << endl;
				}

                char* owner = strchr(end, ':');

                // if there is no colon, it could be ours (if there is a colon it definitely isn't)
                if (owner == 0) {

                    // if the symbol came from an obj, it is an export
					if (!stricmp(lineend - 4, ".obj")) {

                        lineend -= 5;  // point one character before ".obj"
                        while (!isWhitespace(*lineend))
                        {
                            --lineend;
                        }
                        ++lineend;

                        // if the object isn't in the set, add it
                        string object = unify_path(lineend);
                        if (objs.find(object) == objs.end())
                        {
                            objs.insert(object);
                        }
                    
                        bool accept = true;
                        int filterNum = 0;
                        while (accept && filterSymbols[filterNum] != 0) {
                            if (0 != strstr(buf, filterSymbols[filterNum])) {
                                accept = false;
                            }
                            ++filterNum;
                        }

                        if (accept) {
                            *end = '\0';
                            if (C_symbols_have_underscore_prefix && *buf == '_' && !strchr(buf, '@') && !strchr(buf, '?'))    // if it's a C symbol, don't put an _ in the DEF file
                                ++buf;
                            else if (*buf == '.')
                                ++buf;
                            string temp(buf);

                            // strings.push_back(temp);
                            strings[temp] = 0;
                        }

						if (verbose)
						{
							cerr << (accept ? "accepted" : "REJECTED") << " symbol '" << buf << "'" << endl;
						}
                    }
					else if (verbose)
					{
						cerr << "REJECTED" << " symbol '" << buf << "'" << endl;
					}
                }
				else
				{
                    // if the symbol came from a DLL, it is an import
                    if (!stricmp(lineend - 4, ".dll")) {

                        lineend -= 5;  // point one character before ".dll"
                        while (!isWhitespace(*lineend))
                        {
                            --lineend;
                        }
                        ++lineend;

                        // if the object isn't in the set, add it
                        string object(lineend);
                        string::size_type pos = object.find(':');
                        object = unify_path(object.substr(0, pos) + ".lib");
                        if (libs.find(object) == libs.end())
                        {
                            libs.insert(object);

							if (verbose)
							{
								cerr << "collected library '" << object << "'" << endl;
							}
                        }
						else if (verbose)
						{
							cerr << "REJECTED" << " symbol '" << buf << "' which resides in another DLL" << endl;
						}
                    }
					else if (!stricmp(lineend - 4, ".obj"))
					{
	                    // if the symbol came from an obj within a 'static lib', it is an export
                        
						lineend -= 5;  // point one character before ".obj"
                        while (!isWhitespace(*lineend))
                        {
                            --lineend;
                        }
                        ++lineend;

                        // if the object isn't in the set, add it
                        string object(lineend);
                        string::size_type pos = object.find(':');
                        object = unify_path(object.substr(0, pos) + ".lib");
                        if (static_libs.find(object) != static_libs.end())
                        {
							bool accept = true;
							int filterNum = 0;
							while (accept && filterSymbols[filterNum] != 0) 
							{
								if (0 != strstr(buf, filterSymbols[filterNum])) 
								{
									accept = false;
								}
								++filterNum;
							}

							if (accept) 
							{
								*end = '\0';
								if (C_symbols_have_underscore_prefix && *buf == '_' && !strchr(buf, '@') && !strchr(buf, '?'))    // if it's a C symbol, don't put an _ in the DEF file
									++buf;
								else if (*buf == '.')
									++buf;
								string temp(buf);

								// strings.push_back(temp);
								strings[temp] = 0;
							}

							if (verbose)
							{
								cerr << (accept ? "accepted" : "REJECTED") << " static-lib symbol '" << buf << "'" << endl;
							}
						}
						else if (verbose)
						{
							cerr << "REJECTED" << " symbol '" << buf << "'" << endl;
						}
					}
				}

				end = le; // continue looking for symbols on the next line
            }
            buf = end;
        }
    }

	// extract ordinals and external-vs-internal-name exports:
	bool go = false;
	string label_name;
    eof = end_of_file;
    while (buf < eof) 
	{
		char *line_start = buf;

		lineno++;

		while (buf < eof && isWhitespace(*buf))
		{
			buf++;
			if (buf[-1] == '\n')
				break;
		}
		if (buf != line_start && buf[-1] == '\n')
		{
			label_name.clear();
			continue; // extra tweak: keep linecount healthy.
		}

        char *end = buf + strcspn(buf, "\n");
		*end++ = 0;

		if (!go)
		{
			go = !!strstr(buf, "ordinal    name");

			label_name.clear();

			if (verbose)
			{
				cerr << "skipped MAP line " << lineno << " :" << line_start << endl;
			}
        }
		else 
		{
			// two lines are possible here:
			//
			//   11    _xlAddInManagerInfo@4
            //         exported name: xlAddInManagerInfo

			char *ordinal = line_start + strspn(line_start, " \t\r\n");
			if (strncmp(ordinal, "exported name:", 14) == 0)
			{
				// 'internal name' is stored in label_name:
				char *alt_name = ordinal + 14;
				alt_name += strspn(alt_name, " \t\r\n");

				buf = alt_name;
				while (isStartSymbol(buf[0]))
				{
					buf++;
				}
				*buf = 0;

				if (alt_name[0])
				{
					// store alt_name:
                    if (strings.find(label_name) != strings.end())
                    {
                        extra_symbol_info *info = strings[label_name];
						if (!info)
						{
							info = new extra_symbol_info();
						}
						//info->ordinal = ordinal;
						info->export_alt_name = alt_name;
						strings[label_name] = info;

						if (verbose)
						{
							cerr << "assign external alternative name " << alt_name << " to symbol '" << label_name.c_str() << "'" << endl;
						}
                    }
				}
			}
			else if (isdigit(ordinal[0]))
			{
				// ordinal + internal name:
				char *internal_name = ordinal + strcspn(ordinal, " \t\r\n");
				*internal_name++ = 0;

				internal_name += strspn(internal_name, " \t\r\n");
				buf = internal_name;
				buf += strcspn(buf, " \t\r\n");
				*buf++ = 0;

				int ordinal_value = atoi(ordinal);
				if (ordinal_value < 0)
					ordinal_value = 0;
				label_name = internal_name;

				if (ordinal_value > 0)
				{
					// store alt_name:
                    if (strings.find(label_name) != strings.end())
                    {
                        extra_symbol_info *info = strings[label_name];
						if (!info)
						{
							info = new extra_symbol_info();
						}
						info->ordinal = ordinal_value;
						//info->alt_export_name = alt_name;
						strings[label_name] = info;

						if (verbose)
						{
							cerr << "assign ordinal value " << ordinal_value << " to symbol '" << label_name.c_str() << "'" << endl;
						}
                    }
				}
			}
			else
			{
				// illegal line: end of section
				go = false;
			}
		}

		buf = end;
	}
}


static void createResponseFile(string& repFile, string& path, string& moduleName, vector<string>& libpaths, string& implib, set<string>& libs, set<string>& objs, bool noDefaultLibs, bool verbose, string machineType, string manifestFileName)
{
    FILE* r = fopen(repFile.c_str(), "wb");
    if (r != 0)
    {
		fprintf(r, "/OUT:\"%s%s.dll\" /NOLOGO ", terminate_dir(path).c_str(), moduleName.c_str());

        // add boost if the user has it. 
		// TODO: is stage/lib always the right place?
        char* root = getenv("BOOST_ROOT");
        if (root != 0)
            fprintf(r, "/LIBPATH:\"%sstage%clib\" ", terminate_dir(root).c_str(), DIRSEP);

		bool path_seen = false;
        for (size_t i = 0; i < libpaths.size(); ++i)
        {
            // add user lib path
			libpaths[i] = unify_path(libpaths[i]);
            fprintf(r, "/LIBPATH:\"%s\" ", libpaths[i].c_str());
			if (libpaths[i] == path)
				path_seen = true;
        }

        // add .obj path
		if (!path_seen)
		{
			fprintf(r, "/LIBPATH:\"%s\" ", path.c_str());
		}

        // import lib
        fprintf(r, "/IMPLIB:\"%s\" ", implib.c_str());

        // /NODEFAULTLIB:library
        if (noDefaultLibs)
            fprintf(r, "/NODEFAULTLIB ");

        // misc stuff
        fprintf(r, "/DLL /DEBUG /PROFILE /PDB:\"%s%s.pdb\" /DEF:\"%s%s.map.DEF\" /MACHINE:%s ",
            terminate_dir(path).c_str(), moduleName.c_str(),      // PDB
            terminate_dir(path).c_str(), moduleName.c_str(),      // DEF
            machineType.c_str());

        if (!manifestFileName.empty())
        {
            fprintf(r, "/MANIFEST /ALLOWISOLATION /MANIFESTUAC:\"level='asInvoker' uiAccess='false'\" /MANIFESTFILE:\"%s\" ",
                manifestFileName.c_str());
        }

		fprintf(r, "/INCREMENTAL:NO /DYNAMICBASE /ERRORREPORT:QUEUE /NXCOMPAT /LTCG ");
		/*
/MAP 
/MAPINFO:EXPORTS 
/SUBSYSTEM:CONSOLE 
/OPT:NOREF 
/OPT:ICF 
/PGD:"D:\h\prj\1original\ib_tws_if2\build\msvc2010\bin\Win32_MSVC2010.Debug\xlslib_dll.pgd" 
/TLBID:1 
		*/

        for (set<string>::iterator lib = libs.begin(); lib != libs.end(); ++lib)
        {
            fprintf(r, "%s ", (*lib).c_str());
        }

        for (set<string>::iterator obj = objs.begin(); obj != objs.end(); ++obj)
        {
            fprintf(r, "%s ", (*obj).c_str());
        }

        // clean up
        fclose(r);

        if (verbose)
        {
            std::cerr << "Response file '" << repFile << "' content:" << endl;

            r = fopen(repFile.c_str(), "rb");
            if (r != 0)
            {
                fseek(r, 0, SEEK_END);
                int length = ftell(r);
                fseek(r, 0, SEEK_SET);
                char* buf = new char[length+1];
                fread(buf, 1, length, r);
                fclose(r);
                buf[length] = 0;

                char *p = strtok(buf, " \r\n");

                for (; p; p = strtok(NULL, " \r\n"))
                {
                    std::cerr << p << endl;
                }
                std::cerr << "--- end of content ---" << endl;
            }
            else
            {
                std::cerr << "Internal failure! Cannot read contents!" << endl;
            }
        }
    }
    else
    {
        std::cerr << "Unable to open response file " << repFile << endl;
    }
}




static void ProcessExtraDefFile(string includeDefFileName, std::ofstream &logfile, bool verbose, symbol_store& strings)
{
    if (includeDefFileName.size() > 0)
    {
		if (verbose)
		{
			cerr << "Loading DEF file: '" << includeDefFileName.c_str() << "'" << endl;
		}

        FILE* includeDefFile = fopen(includeDefFileName.c_str(), "rb");
        if (includeDefFile != 0)
        {
            // read the def file in
            fseek(includeDefFile, 0, SEEK_END);
            int length = ftell(includeDefFile);
            fseek(includeDefFile, 0, SEEK_SET);
            char* buf = new char[length+1];
            fread(buf, 1, length, includeDefFile);
            fclose(includeDefFile);
			buf[length] = 0;


            char* eof = buf + length;
            char* s = buf;
            char* nl = eof;

            for( ; s < eof; s = nl + 1)
            {
                // cut up into lines.
                size_t pos = strcspn(s, "\r\n");
                s[pos] = 0;
                nl = &s[pos];

                // is line a command? ignore!
                if (!isspace(*s))
                    continue;

                while (*s && isspace(*s))
                    s++;

                // is line empty or a comment? ignore!
                if (*s == ';' || *s == '#' || !*s)
                    continue;

                // does line contain an 'ordinal'? No? ignore!
                char *identifier = s;
                while (*s && !isspace(*s))
                    s++;
                *s++ = 0;
                while (s < nl && isspace(*s))
                    s++;
                if (*s != '@')
                    continue;
                s++;
                int ordinal = atoi(s);
				if (ordinal < 0)
					ordinal = 0;

				char *alt_name = identifier + strcspn(identifier, "=");
				if (*alt_name)
				{
					*alt_name++ = 0;
				}
                bool accept = (ordinal > 0) || alt_name[0];
                int filterNum = 0;
                while (accept && filterSymbols[filterNum] != 0)
                {
                    if (0 != strstr(identifier, filterSymbols[filterNum]))
                    {
                        accept = false;
                    }
                    ++filterNum;
                }

                if (accept)
                {
                    string temp(identifier);

                    if (strings.find(temp) != strings.end())
                    {
                        extra_symbol_info *info = strings[temp];
						if (!info)
						{
							info = new extra_symbol_info();
						}
						info->ordinal = ordinal;
						info->export_alt_name = alt_name;
						strings[temp] = info;

						if (verbose)
						{
							cerr << "assign ordinal " << ordinal << " to symbol '" << temp.c_str() << "'" << endl;
						}
                    }
                }
                else
                {
                    logfile << "Unknown symbol (from include DEF file) '" << identifier << "' has ordinal: " << ordinal << endl;
                    cerr << "Unknown symbol (from include DEF file) '" << identifier << "' has ordinal: " << ordinal << endl;
                }
            }

            delete[] buf;
        }
        else
        {
            logfile << "Cannot open DEF file for inclusion: '" << includeDefFileName.c_str() << "'" << endl;
            cerr << "Cannot open DEF file for inclusion: '" << includeDefFileName.c_str() << "'" << endl;
        }
    }
}




int main(int argC, char* argV[])
{
    int status = -1;
    const char *version_string = "CreateDLL v1.2.7";

    OptionParser options(argV[0]);

    std::cout << version_string << std::endl;

    vector<string> libpaths;
    options.AddStringVectorOption("-l", "--libdir", libpaths, "semi-colon separated lib dirs");
    vector<string> morelibs;
    options.AddStringVectorOption("-o", "--morelibs", morelibs, "comma / semi-colon separated additional libs");
    vector<string> exporting_staticlibs;
    options.AddStringVectorOption("-e", "--exportinglibs", exporting_staticlibs, "additional (static) libs which are exporting symbols through the current target (DLL)");
    string importlib;
    options.AddStringOption("-i", "--importlib", importlib, "the import lib to go with the DLL");
    string mapFileName;
    options.AddStringOption("-n", "--mapfile", mapFileName, "name of map file to use");
    bool noDefaultLib = false;
    options.AddTrueOption("-d", "--nodefaultlib", noDefaultLib, "invoke /NODEFAULTLIB:library option");
    bool verbose = false;
    options.AddTrueOption("-v", "--verbose", verbose, "verbose diagnostic output");
    string machineType = "X86";
    options.AddStringOption("-M", "--machine", machineType, "machine name: X86, X64, IA64, ... as supported by Microsoft Linker");
    string manifestFileName = "*";
    options.AddOptionalStringOption("-m", "--manifest", manifestFileName, "name of manifest file to generate");
    vector<string> includeDefFileNames;
    options.AddStringVectorOption("-I", "--include-def-file", includeDefFileNames, "names of DEF format files to include and assign ordinals to symbols");
    bool skip_link_task = false;
    options.AddTrueOption("-s", "--skip", skip_link_task, "skip link invocation (can be used to test behaviour a if CreateDLL was disabled)");
    string workDir;
    options.AddStringOption("-W", "--workdir", workDir, "path of the work directory where response files, etc. can be stored.");

    // make a command line less argV[0] which is the executable path
    std::string commandLine = Join(argC-1, &argV[1], " ");
    options.Parse(commandLine);

	// translate certain machineTypes so we can say something like this in PostBuild entry of DLL projects in MSVC IDE:
	//
	//  ..\..\..\..\..\Deploy\bin\Win32_$(ConfigurationName)\createDLL -M$(PlatformName) -n$(OutDir)\$(ProjectName).map -l$(IntDir) -i$(OutDir)\$(ProjectName).lib
	//  ..\..\..\install$(ProjectName).cmd $(IntDir) $(PlatformName)_$(ConfigurationName) $(SolutionDir)bin\$(PlatformName)_$(ConfigurationName)
    //
	// where $(PlatformName) = { 'x86', 'Win32', 'Itanium' };
	// and MSVC2005 supports: 'ARM, EBC, IA64, MIPS, MIPS16, MIPSFPU, MIPSFPU16, SH4, THUMB, X64, or X86'
	if (machineType == "x64")
	{
		machineType = "X64";
	}
	else if (machineType == "Itanium")
	{
		machineType = "IA64";
	}
	else if (machineType == "Win32")
	{
		machineType = "X86";
	}

    string::size_type pos;

	if (workDir.empty())
	{
		char tempDir[MAX_PATH];
		int rt = GetTempPathA(MAX_PATH, tempDir);

		// 20 less because we have to have room for the filename
		if (rt == 0 || rt > MAX_PATH - 20)
		{
			std::cerr << "Failed to get temp dir\n";
			return 1;
		}
		workDir.assign(tempDir);
	}
	workDir = terminate_dir(workDir);

    std::string repFile(workDir);
    repFile += "*.response.txt";
	repFile = mk_tempname(repFile);

    std::string logName(workDir);
    logName += "*.createDLL_out.txt";
	logName = mk_tempname(logName);

    std::cout << "Linker response file is " << repFile << endl;
    std::cout << "Log file is " << logName << endl;

    // banner
    std::ofstream logfile(logName.c_str(), std::ios::out);
    logfile << version_string << endl;
    logfile << "createDLL was built " << __DATE__ << " " << __TIME__ << endl;

    if (mapFileName.empty() || importlib.empty())
    {
        options.Usage();
    }
    else
    {
		mapFileName = unify_path(mapFileName);
        string mapName = mapFileName; //GetArg("-n", args);
        const char* ext = strrchr(mapName.c_str(), '.');
        if (ext == 0)
        {
            mapName += ".MAP";
        }

        FILE* mapFile = fopen(mapName.c_str(), "rb");
        if (mapFile != 0)
        {
            logfile << "Processing " << mapName << std::endl;

            // read the map file in
            fseek(mapFile, 0, SEEK_END);
            int length = ftell(mapFile);
            fseek(mapFile, 0, SEEK_SET);
            char* buf = new char[length+1];
            fread(buf, 1, length, mapFile);
            fclose(mapFile);
			buf[length] = 0;

            // get symbols
            symbol_store symbols;
            // get object files to link
            set<string> objs;
            // get libs to link against
            set<string> libs;
			// the optional static libs which place additional symbols into the DLL
            set<string> static_libs;
            addLibsFromVector(static_libs, exporting_staticlibs);

            getSymbolsObjsAndLibsFromMap(buf, length, (machineType == "X86"), verbose, symbols, objs, libs, static_libs);

            //vector<string> morelibs;
            //GetArgs("-o", args, morelibs);

            // add libs from the command line
            addLibsFromVector(libs, morelibs);

            // assign ordinals listed in another DEF file for any symbols we know:
			for (size_t i = 0; i < includeDefFileNames.size(); ++i)
			{
				// one DEF file for processing
	            ProcessExtraDefFile(unify_path(includeDefFileNames[i]), logfile, verbose, symbols);
			}

            // helpful info in log
            logfile << "Input Object files: " << endl;
            for (set<string>::iterator objIter = objs.begin(); objIter != objs.end(); ++objIter)
            {
                logfile << *objIter << std::endl;
            }

            // helpful info in log
            logfile << "Input Libraries: " << endl;
            for (set<string>::iterator objIter = libs.begin(); objIter != libs.end(); ++objIter)
            {
                logfile << *objIter << std::endl;
            }


            // create the def file
            string defName = mapName + ".DEF";
            FILE* defFile = fopen(defName.c_str(), "wb");

            // get the module name from the map name
            char const* sub = strrchr(mapName.c_str(), DIRSEP);
            if (sub == 0) sub = mapName.c_str();
            if (*sub == DIRSEP) ++sub;
            string moduleName(sub);
            if (strrchr(sub, '.'))
            {
                moduleName = moduleName.substr(0, moduleName.find_last_of('.'));
            }

            // write out the def file
            createDef(moduleName, symbols, verbose, defFile);

            // close files
            fclose(defFile);

            // strip module name off map path
            string path(mapName);
            pos = path.rfind(DIRSEP);
            if (pos != string::npos)
                path = path.substr(0, pos);

            // create a response file
            //vector<string> libpaths;
            //GetArgs("-l", args, libpaths);

            // if there is a .lib at the end, strip it off,
            // because in the future I probably want to use
            // the name of the implib to generate the pdb as well
            // I haven't done that yet.
            //string implib = GetArg("-i", args);
            pos = importlib.find(".lib");
            if (pos != string::npos)
            {
                importlib = importlib.substr(0, pos);
            }
			importlib = unify_path(importlib);

            // silly placeholder code, given the comment above :
            string impliblib = importlib + ".lib";

            if (manifestFileName == "*")
            {
                manifestFileName = "";
            }
            else if (manifestFileName.empty())
            {
                manifestFileName = importlib + ".dll.createDLL.manifest";
            }
			else
			{
				manifestFileName = unify_path(manifestFileName);
			}
            createResponseFile(repFile, path, moduleName, libpaths, impliblib, libs, objs, noDefaultLib, verbose, machineType, manifestFileName);

            // invoke linker
            cerr << "Exporting DLL" << endl;

            // run the linker
            char**  new_argv = new char *[3];
            size_t  new_argc = 0;
            std::string repFileArg("@");
            repFileArg += repFile;

            new_argv[0] = "createDLL";
            new_argv[1] = (char*)repFileArg.c_str();
            new_argv[2] = 0;

            const char* linkerString = getenv("MSVCLINK");
            string nativeLinker;
            if ( linkerString == NULL )
                linkerString = "link.exe";
            if (linkerString != 0)
            {
                nativeLinker.assign(linkerString);
				if (!skip_link_task)
				{
					status = (int) _spawnvp(_P_WAIT, nativeLinker.c_str(), new_argv);
				}
				else
				{
					logfile << "SKIPPING LINKER invocation: " << nativeLinker.c_str() << " " << repFileArg.c_str() << endl;
					cerr << "SKIPPING LINKER invocation: " << nativeLinker.c_str() << " " << repFileArg.c_str() << endl;

					status = 0;
				}
            }
            else
            {
                MessageBoxA(0, "createDLL: MSVCLINK environment variable must set to the full path to the msvc linker", "CreateDLL", MB_OK);

                logfile << "MSVCLINK environment variable must be set to the full path to the msvc linker?" << endl;
                cerr << "MSVCLINK environment variable must be set to the full path to the msvc linker?" << endl;
                status = 1;
            }

            if (linkerString == 0 || status == -1)
            {
                MessageBoxA(0, "createDLL: Failure to start linker.", "CreateDLL", MB_OK);

                logfile << "createDLL: Failure to start linker named: " << nativeLinker << " (does your PATH include it?)" << endl;
                cerr << "createDLL: Failure to start linker named: " << nativeLinker << " (does your PATH include it?)" << endl;
                status = 1;
            }
            else if (status != 0)
            {
                logfile << "createDLL: linking failure. Linker: " << (linkerString ? linkerString : "---") << ", status: " << status << endl;
                cerr << "createDLL: linking failure. Linker: " << (linkerString ? linkerString : "---") << ", status: " << status << endl;
                //status = 1;
            }
            else
            {
				cerr << "createDLL: linking success. Linker: " << (linkerString ? linkerString : "---") << ", status: " << status << endl;
            }

            // clean up
            delete [] new_argv;
            delete [] buf;
        }
        else
        {
            logfile << "Specified mapfile " << mapName << " not found" << endl;
            std::cerr << "Specified mapfile " << mapName << " not found" << endl;
        }
    }

    // end log
    logfile << endl;
    logfile.close();

	return (status < 0 ? EXIT_FAILURE : status);
}
